/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

	// New index Code:
	BTreeIndex bti;			// Stores B+tree for table data
	int searchKey;			// Select command line key to find.
	char * searchValue;		// Select command line value to find.

	// Stores keys and values for multiple iterations through conditions
	int keyEQ;
	//vector<int> keyNEVector;	// Can have multiple NE conditions
	int keyMin;
	int keyMax;

	int noKeyEQ = 0;		// No equal conditions
	int noKeyMin = 0;		// No Min conditions
	int noKeyMax = 0;		// No Max conditions
	int noIndex = 0;		// Use to bypass index file
	int locateKey;			// Key for locate function.
	IndexCursor cursor;		// Cursor (pid & eid) for locate and readForward methods

	// For the select command, it is not an error if the index file
	// does not exist. Only use the B+tree if the index file exists.
	if ((rc = bti.open(table + ".idx", 'r')) == 0)
	{

		// loop through each condition
	    for (unsigned i = 0; i < cond.size(); i++)
		{
			if (cond[i].attr == 1)			// condition is a key
			{
				searchKey = atoi(cond[i].value);
				// skip the tuple if any condition is not met
				switch (cond[i].comp)
				{
					case SelCond::EQ:
						if (noKeyEQ == 0)				// load keyEQ for first equal condition
						{
							keyEQ = searchKey;
							noKeyEQ = 1;
						}
						else if (keyEQ != searchKey)	// return if have 2 equal conditions
							return 0;
						break;
					case SelCond::NE:
						//keyNEVector.push_back(searchKey);	// Need to check all NE conditions
						break;
					case SelCond::GT:
						if (noKeyMin == 0)
						{
							keyMin = searchKey + 1;		// cannot include equal to
							noKeyMin = 1;
						}
						else if (keyMin < searchKey)	// need maximum GT condition key
							keyMin = searchKey + 1;
						break;	
					case SelCond::LT:
						if (noKeyMax == 0)
						{
							keyMax = searchKey - 1;		// cannot include equal to
							noKeyMax = 1;
						}
						else if (keyMax > searchKey)	// need minimum LT condition key
							keyMax = searchKey - 1;
						break;
					case SelCond::GE:
						if (noKeyMin == 0)
						{
							keyMin = searchKey;			// need to include equal to
							noKeyMin = 1;
						}
						else if (keyMin < searchKey)	// need maximum GE condition key
							keyMin = searchKey;
						break;
					case SelCond::LE:
						if (noKeyMax == 0)
						{
							keyMax = searchKey;			// need to include equal to
							noKeyMax = 1;
						}
						else if (keyMax > searchKey)	// need minimum LE condition key
							keyMax = searchKey;
						break;
				}
			}
			//cout << "searchKey = " << searchKey << endl;
		}
		//cout << "keyEQ = " << keyEQ << endl;
		//cout << "keyMin = " << keyMin << endl;
		//cout << "keyMax = " << keyMax << endl;
		//cout << "noKeyMax = " << noKeyMax << endl;

		// Determine key to use for locate function
		// 2^3=8 key cases
		if (noKeyEQ == 0 && noKeyMin == 0 && noKeyMax == 0) // dont use index file if no key, unless count(*)
		{
			if (attr != 4)
				noIndex = 1;
			else						// this is a count(*)
			{
				noKeyMin = 1;			// do a GE search to the end of the B+ tree
				locateKey = 0;			// Need to start search at 0
			}
		}
		else if (noKeyEQ != 0 && noKeyMin == 0 && noKeyMax == 0)
			locateKey = keyEQ;
		else if (noKeyEQ == 0 && noKeyMin != 0 && noKeyMax == 0)
			locateKey = keyMin;
		else if (noKeyEQ == 0 && noKeyMin == 0 && noKeyMax != 0)
			locateKey = 0;				// if only max search, need to set min to 0
		else if (noKeyEQ != 0 && noKeyMin != 0 && noKeyMax == 0)
		{
			if (keyEQ < keyMin)			// Impossible condition, so return no values
				return 0;
			locateKey = keyEQ;			// Only 1 key output
		}
		else if (noKeyEQ != 0 && noKeyMin == 0 && noKeyMax != 0)
		{
			if (keyEQ > keyMax)			// Impossible condition, so return no values
				return 0;
			locateKey = keyEQ;			// Only 1 key output
		}
		else if (noKeyEQ == 0 && noKeyMin != 0 && noKeyMax != 0)
		{
			if (keyMin > keyMax)		// Impossible condition, so return no values
				return 0;
			locateKey = keyMin;
		}
		else if (noKeyEQ != 0 && noKeyMin != 0 && noKeyMax != 0)
		{
			if (keyEQ < keyMin || keyEQ > keyMax)
				return 0;
			locateKey = keyEQ;			// Only 1 key output
		}
		//cout << "locateKey = " << locateKey << endl;



		// Bypass index file if no key conditions
		count = 0;
		if (noIndex == 0)
		{
			// Outputs cursor
			if ((rc = bti.locate(locateKey, cursor)) < 0) 
			{
				fprintf(stderr, "Error: Key was not found in leaf node.\n");
				return rc;
			}
			//cout << "cursor.pid = " << cursor.pid << endl;
			//cout << "cursor.eid = " << cursor.eid << endl;

			// do once for keyEQ
			// iterate for keyMin & keyMax
			int readForwardEnd = 0;			// Added to control loop so output last table entry!
			while (readForwardEnd == 0)
			{
				
				// Outputs key & rid
				if ((rc = bti.readForward(cursor, key, rid)) < 0) 
				{
					//fprintf(stderr, "Error: readForward failed.\n");
					//return 0;
					readForwardEnd = 1;
				}

				//cout << "rid.pid = " << rid.pid << endl;
				//cout << "rid.sid = " << rid.sid << endl;

				// Break out of the loop before keyMax value.
				if (noKeyMax != 0 && key > keyMax)
				{
					//cout << "Break on keyMax!" << endl;
					break;
				}
				
				// Don't read recordFile unless need to extract value
				if (attr == 2 || attr == 3)
				{
					// Read rf with the readForward rid
					if ((rc = rf.read(rid, key, value)) < 0)
					{
						fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
						bti.close();
						rf.close();
						return rc;
					}
					//cout << "key = " << key << endl;
					//cout << "value = " << value << endl;
				}

			    // check the conditions on the tuple
				for (unsigned i = 0; i < cond.size(); i++)
				{
					// compute the difference between the tuple value and the condition value
					switch (cond[i].attr)
					{
						case 1:
							diff = key - atoi(cond[i].value);
							break;
						case 2:
							diff = strcmp(value.c_str(), cond[i].value);
							break;
					}

					// skip the tuple if any condition is not met
					switch (cond[i].comp)
					{
						case SelCond::EQ:
							if (diff != 0) goto no_print;
							break;
						case SelCond::NE:
							if (diff == 0) goto no_print;
							break;
						case SelCond::GT:
							if (diff <= 0) goto no_print;
							break;
						case SelCond::LT:
							if (diff >= 0) goto no_print;
							break;
						case SelCond::GE:
							if (diff < 0) goto no_print;
							break;
						case SelCond::LE:
							if (diff > 0) goto no_print;
							break;
					}
				}

			    // the condition is met for the tuple. 
				// increase matching tuple counter
				count++;

				// print the tuple 
				switch (attr)
				{
					case 1:  // SELECT key
						fprintf(stdout, "%d\n", key);
						break;
					case 2:  // SELECT value
						fprintf(stdout, "%s\n", value.c_str());
						break;
					case 3:  // SELECT *
						fprintf(stdout, "%d '%s'\n", key, value.c_str());
						break;
				}

			    // continue with readForward but bypass count++ and print
				no_print:

				// Break out of loop for keyEQ
				if (noKeyEQ == 1)
				{
					//cout << "Break on noKeyEQ!" << endl;
					break;
				}
			}

			// print matching tuple count if "select count(*)"
			if (attr == 4)
			{
				fprintf(stdout, "%d\n", count);
			}

			// close the table file and return
			bti.close();
			rf.close();
			return 0;
		}
	}



  // Original select code below.
  // Need to run if no index file.

  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
	diff = key - atoi(cond[i].value);
	break;
      case 2:
	diff = strcmp(value.c_str(), cond[i].value);
	break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
	if (diff != 0) goto next_tuple;
	break;
      case SelCond::NE:
	if (diff == 0) goto next_tuple;
	break;
      case SelCond::GT:
	if (diff <= 0) goto next_tuple;
	break;
      case SelCond::LT:
	if (diff >= 0) goto next_tuple;
	break;
      case SelCond::GE:
	if (diff < 0) goto next_tuple;
	break;
      case SelCond::LE:
	if (diff > 0) goto next_tuple;
	break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  /* your code here */

	RC rc;					// Added for parseLoadLine errors
	string line;
	int key;
	string value;
	RecordId rid;
	
	RecordFile rf;			// Stores data from file read
	BTreeIndex bti;			// Stores B+tree for table data

	// open the RecordFile table file
	if ((rc = rf.open(table + ".tbl", 'w')) < 0) {
		fprintf(stderr, "Error: table %s could not be opened.\n", table.c_str());
		return rc;
	}


	// open the index file
	if (index == true)
	{
		if ((rc = bti.open(table + ".idx", 'w')) < 0) 
		{
			fprintf(stderr, "Error: index %s could not be opened.\n", table.c_str());
			return rc;
		}
	}

	ifstream fin(loadfile.c_str());	// Opens load file
	if (fin.is_open())
	{
		while (fin.good())
		{
			getline(fin, line);
			//cout << line << endl;
			if ((rc = parseLoadLine(line, key, value)) < 0)		// Parse line and output key & value
			{
				if (!fin.eof())				// Added so that parseLoadLine would not return an
				{							// error on the last line of the movie.del file!!!
					fprintf(stderr, "Error: file %s could not be parsed for keys/values\n", loadfile.c_str());
					return rc;
				}
				else
					return 0;
			}
			if ((rc = rf.append(key, value, rid)) < 0)			// Append key & value to table file
			{
				fprintf(stderr, "Error: RecordFile %s could not be appended\n", table.c_str());
				return rc;
			}
			if (index == true)
			{
				//cout << "key = " << key << endl;
				if ((rc = bti.insert(key, rid)) < 0)			// Insert key & rid into index file
				{
					fprintf(stderr, "Error: BTreeIndex %s could not be inserted\n", table.c_str());
					return rc;
				}
			}
		
		}
		fin.close();
	}
	else
	{
		fprintf(stderr, "Error: file %s could not be opened\n", loadfile.c_str());
		return RC_FILE_OPEN_FAILED;
	}

	// close the table file and return
	if (index == true)
		bti.close();
	rf.close();
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
